﻿/*-----------------------------------------------------------------------------------------
   Author               Date            Description
   ----------------------------------------------------------------------------------------
   Steven Applegate     01/30/2011      Initial creation
 ----------------------------------------------------------------------------------------*/

using Elmah;
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using NameValueCollection = System.Collections.Specialized.NameValueCollection;

namespace SuddenDeath.Domain.Services.Render.ELMAH
{
    public class ElmahRenderer
    {
        private static readonly Regex RegexStackTrace = new Regex(@"
                ^
                \s*
                \w+ \s+ 
                (?<type> .+ ) \.
                (?<method> .+? ) 
                (?<params> \( (?<params> .*? ) \) )
                ( \s+ 
                \w+ \s+ 
                  (?<file> [a-z] \: .+? ) 
                  \: \w+ \s+ 
                  (?<line> [0-9]+ ) \p{P}? )?
                \s*
                $",
            RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture |
            RegexOptions.CultureInvariant | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for an ELMAH error
        /// </summary>
        public static string RenderError(Guid errorId)
        {
            var stringWriter = new StringWriter();
            ErrorLogEntry errorLogEntry = ErrorLog.GetDefault(HttpContext.Current).GetError(errorId.ToString());

            Error error = errorLogEntry.Error;

            using (var writer = new HtmlTextWriter(stringWriter))
            {
                // write out the page title containing error type and message
                writer.AddAttribute(HtmlTextWriterAttribute.Id, "PageTitle");
                writer.RenderBeginTag(HtmlTextWriterTag.H1);
                HttpUtility.HtmlEncode(error.Message, writer);
                writer.RenderEndTag(); // </h1>

                // write out header row
                writer.RenderBeginTag(HtmlTextWriterTag.Hr);
                writer.RenderEndTag(); // </hr>
                writer.WriteLine();

                // write out error title
                writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorTitle");
                writer.RenderBeginTag(HtmlTextWriterTag.P);

                // write out error type
                writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorType");
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
                HttpUtility.HtmlEncode(error.Type, writer);
                writer.RenderEndTag(); // </span>

                writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorTypeMessageSeparator");
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
                writer.Write(": ");
                writer.RenderEndTag(); // </span>

                // write out error message
                writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorMessage");
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
                HttpUtility.HtmlEncode(error.Message, writer);
                writer.RenderEndTag(); // </span>

                // write the date and time the error occurred
                writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorLogTime");
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
                HttpUtility.HtmlEncode(string.Format("Logged on {0} at {1}",
                                         error.Time.ToLongDateString(),
                                         error.Time.ToLongTimeString()), writer);
                writer.RenderEndTag(); // </span>
                writer.WriteLine();

                writer.RenderEndTag(); // </p>
                writer.WriteLine();

                // if we have error details (like the stack trace), write them out
                if (error.Detail.Length != 0)
                {
                    writer.AddAttribute(HtmlTextWriterAttribute.Id, "ErrorDetail");
                    writer.RenderBeginTag(HtmlTextWriterTag.Div);
                    writer.RenderBeginTag(HtmlTextWriterTag.Pre);
                    writer.Flush();
                    MarkupStackTrace(error.Detail, writer.InnerWriter);
                    writer.RenderEndTag(); // </pre>
                    writer.RenderEndTag(); // </div>
                    writer.WriteLine();
                }

                // If this error has context, then write it out
                RenderCollection(writer, error.ServerVariables, "ServerVariables", "Server Variables");
            }

            return stringWriter.ToString();
        }



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a collection
        /// </summary>
        private static void RenderCollection(HtmlTextWriter writer, NameValueCollection collection, string id, string title)
        {
            // if the collection isn't there or it's empty, then bail out
            if (collection == null || collection.Count == 0)
                return;

            // surround the entire section with a <div> element
            writer.AddAttribute(HtmlTextWriterAttribute.Id, id);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);

            // write out the table caption
            writer.RenderBeginTag(HtmlTextWriterTag.H2);
            HttpUtility.HtmlEncode(title, writer);
            writer.RenderEndTag(); // </h2>
            writer.WriteLine();

            // for formatting, encapsulate the table into a scrollable view 
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "scroll-view");
            writer.RenderBeginTag(HtmlTextWriterTag.Div);

            // create a table to display the name/value pairs of the collection in 2 columns
            var table = new Table { CellSpacing = 0 };

            // create the header row and columns
            var headRow = new TableRow();

            var headCell = new TableHeaderCell { Wrap = false, Text = "Name", CssClass = "name-col" };

            headRow.Cells.Add(headCell);

            headCell = new TableHeaderCell { Wrap = false, Text = "Value", CssClass = "value-col" };

            headRow.Cells.Add(headCell);

            table.Rows.Add(headRow);

            // create a row for each entry in the collection
            string[] keys = collection.AllKeys;
            Array.Sort(keys, StringComparer.InvariantCulture);

            for (int keyIndex = 0; keyIndex < keys.Length; keyIndex++)
            {
                string key = keys[keyIndex];

                var bodyRow = new TableRow
                                  {
                                      CssClass = keyIndex%2 == 0 ? "even-row" : "odd-row"
                                  };

                // create the key column
                var cell = new TableCell { Text = HttpUtility.HtmlEncode(key), CssClass = "key-col" };

                bodyRow.Cells.Add(cell);

                // create the value column
                cell = new TableCell { Text = HttpUtility.HtmlEncode(collection[key]), CssClass = "value-col" };

                bodyRow.Cells.Add(cell);

                table.Rows.Add(bodyRow);
            }

            // write out the table and close container tags
            table.RenderControl(writer);

            writer.RenderEndTag(); // </div>
            writer.WriteLine();

            writer.RenderEndTag(); // </div>
            writer.WriteLine();
        }

        

        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a stack trace
        /// </summary>
        private static void MarkupStackTrace(string text, TextWriter writer)
        {
            int anchor = 0;

            foreach (Match match in RegexStackTrace.Matches(text))
            {
                HttpUtility.HtmlEncode(text.Substring(anchor, match.Index - anchor), writer);
                MarkupStackFrame(text, match, writer);
                anchor = match.Index + match.Length;
            }

            HttpUtility.HtmlEncode(text.Substring(anchor), writer);
        }



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a stack trace
        /// </summary>
        private static void MarkupStackFrame(string text, Match match, TextWriter writer)
        {
            int anchor = match.Index;
            GroupCollection groups = match.Groups;

            // type + method
            Group type = groups["type"];
            HttpUtility.HtmlEncode(text.Substring(anchor, type.Index - anchor), writer);
            anchor = type.Index;
            writer.Write("<span class='st-frame'>");
            anchor = StackFrameSpan(text, anchor, "st-type", type, writer);
            anchor = StackFrameSpan(text, anchor, "st-method", groups["method"], writer);

            // parameters
            Group parameters = groups["params"];
            HttpUtility.HtmlEncode(text.Substring(anchor, parameters.Index - anchor), writer);
            writer.Write("<span class='st-params'>(");
            int position = 0;
            foreach (string parameter in parameters.Captures[0].Value.Split(','))
            {
                int spaceIndex = parameter.LastIndexOf(' ');
                if (spaceIndex <= 0)
                {
                    Span(writer, "st-param", parameter.Trim());
                }
                else
                {
                    if (position++ > 0)
                        writer.Write(", ");
                    string argType = parameter.Substring(0, spaceIndex).Trim();
                    Span(writer, "st-param-type", argType);
                    writer.Write(' ');
                    string argName = parameter.Substring(spaceIndex + 1).Trim();
                    Span(writer, "st-param-name", argName);
                }
            }
            writer.Write(")</span>");
            anchor = parameters.Index + parameters.Length;

            // file + line
            anchor = StackFrameSpan(text, anchor, "st-file", groups["file"], writer);
            anchor = StackFrameSpan(text, anchor, "st-line", groups["line"], writer);

            writer.Write("</span>");

            // epilogue
            int end = match.Index + match.Length;
            HttpUtility.HtmlEncode(text.Substring(anchor, end - anchor), writer);
        }



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a span element
        /// </summary>
        private static int StackFrameSpan(string text, int anchor, string klass, Group group, TextWriter writer)
        {
            return group.Success
                 ? StackFrameSpan(text, anchor, klass, group.Value, group.Index, group.Length, writer)
                 : anchor;
        }



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a span element
        /// </summary>
        private static int StackFrameSpan(string text, int anchor, string klass, string value, int index, int length, TextWriter writer)
        {
            HttpUtility.HtmlEncode(text.Substring(anchor, index - anchor), writer);
            Span(writer, klass, value);
            return index + length;
        }



        /// <summary>
        /// Author: Steven Applegate (01/30/2011)
        /// Description: Renders HTML for a span element
        /// </summary>
        private static void Span(TextWriter writer, string klass, string value)
        {
            writer.Write("<span class='");
            writer.Write(klass);
            writer.Write("'>");
            HttpUtility.HtmlEncode(value, writer);
            writer.Write("</span>");
        }
    }
}
